package ua.org.nuos.sdms.middle.service;

import org.apache.commons.lang.Validate;
import ua.org.nuos.sdms.crypt.Cipher;
import ua.org.nuos.sdms.crypt.CipherFactory;
import ua.org.nuos.sdms.middle.dao.GroupDaoBean;
import ua.org.nuos.sdms.middle.dao.UserDaoBean;
import ua.org.nuos.sdms.middle.dao.UserToGroupDaoBean;
import ua.org.nuos.sdms.middle.entity.Group;
import ua.org.nuos.sdms.middle.entity.User;
import ua.org.nuos.sdms.middle.entity.UserToGroup;
import ua.org.nuos.sdms.middle.entity.enums.Role;
import ua.org.nuos.sdms.middle.util.exception.DuplicateEmailException;
import ua.org.nuos.sdms.middle.util.exception.OneGroupLeaderException;

import javax.annotation.PostConstruct;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created with IntelliJ IDEA.
 * User: dio
 * Date: 8/24/12
 * Time: 4:48 PM
 * To change this template use File | Settings | File Templates.
 */
@Stateless
@RolesAllowed({Role.administrator, Role.client})
public class UserServiceBean implements UserServiceLocal {

    @EJB(beanName = "UserDaoBean")
    private UserDaoBean userDao;

    @EJB(beanName = "GroupDaoBean")
    private GroupDaoBean groupDao;

    @EJB(beanName = "UserToGroupDaoBean")
    private UserToGroupDaoBean userToGroupDao;

    @EJB(beanName = "AuthenticationBean")
    private AuthenticationLocal authentication;

    private Cipher cipher;

    @PostConstruct
    public void before() {
        cipher = CipherFactory.getCipher();
    }

    /**
     * Update existing user
     * password does not changed
     *
     * @param user
     * @throws DuplicateEmailException
     */
    @Override
    public void updateCurrentUser(User user) throws DuplicateEmailException {
        Validate.notNull(user);

        long currentUserId = authentication.getCurrentUser().getId();
        if (currentUserId != user.getId()) {
            throw new RuntimeException("Not current user");
        }

        if (userDao.findByEmail(user.getEmail()) != null) {
            throw new DuplicateEmailException(user.getEmail());
        }
        User old = userDao.findById(user.getId());
        user.setPassword(old.getPassword());
        userDao.update(user);
    }

    /**
     * Change user password
     *
     * @param password
     */
    @Override
    public void changeCurrentUserPassword(String password) {
        User user = authentication.getCurrentUser();
        if (user != null) {
            password = cipher.cipher(password);
            user.setPassword(password);
            userDao.update(user);
        }
    }

    /**
     * Find user by id
     *
     * @param userId
     * @return
     */
    @Override
    public User findUserById(long userId) {
        User user = userDao.findById(userId);
        user.setPassword("");
        return user;
    }

    /**
     * Find user by email
     *
     * @param userEmail
     * @return
     */
    @Override
    public User findUserByEmail(String userEmail) {
        User user = userDao.findByEmail(userEmail);
        user.setPassword("");
        return user;
    }

    /**
     * Find user by name
     *
     * @param name
     * @return
     */
    @Override
    public List<User> listUsersByName(String name) {
        List<User> users = userDao.findByName(name);
        for (User user : users) {
            user.setPassword("");
        }
        return users;
    }

    @Override
    public List<User> listUsersByGroup(long groupId) {
        Set<User> users = new HashSet<User>();
        for (UserToGroup userToGroup : userToGroupDao.findByGroup(groupId)) {
            users.add(userToGroup.getUser());
        }
        return new ArrayList<User>(users);
    }

    /**
     * Remove user
     */
    @Override
    public void removeCurrentUser() throws OneGroupLeaderException {
        long currentUserId = authentication.getCurrentUser().getId();
        List<UserToGroup> utgList = userToGroupDao.findByUser(currentUserId);
        final int minLeadersInGroup = 2;
        for (UserToGroup userToGroup : utgList) {
            if (userToGroup.getStatus().equals(UserToGroup.Status.LEADER)) {
                Group group = userToGroup.getGroup();
                List<UserToGroup> list = userToGroupDao.findByGroupAndStatus(group.getId(), UserToGroup.Status.LEADER);
                if (list.size() < minLeadersInGroup) {
                    throw new OneGroupLeaderException(group);
                }
            }
        }
        for (UserToGroup userToGroup : utgList) {
            userToGroupDao.remove(userToGroup);
        }
        userDao.remove(currentUserId);
    }

    /**
     * Add role to user
     *
     * @param userId
     * @param role
     */
    @Override
    @RolesAllowed(Role.administrator)
    public void addRole(long userId, Role role) {
        Validate.isTrue(userId > 0);
        Validate.notNull(role);

        User user = userDao.findById(userId);
        Validate.notNull(user);

        if (canUserBeInRole(userId, role)) {
            return;
        }

        user.getRoles().add(role);
        userDao.update(user);
    }

    /**
     * Remove role from user
     *
     * @param userId
     * @param role
     */
    @Override
    @RolesAllowed(Role.administrator)
    public void removeRole(long userId, Role role) {
        Validate.isTrue(userId > 0);
        Validate.notNull(role);

        User user = userDao.findById(userId);
        Validate.notNull(user);

        if (user.getRoles().contains(role)) {
            user.getRoles().remove(role);
            userDao.update(user);
        }
    }

    @Override
    @RolesAllowed(Role.administrator)
    public void removeAllRoles(long userId) {
        for (Role role : Role.values()) {
            removeRole(userId, role);
        }
    }

    /**
     * Check if user can be in
     *
     * @param userId
     * @param role
     * @return
     */
    @Override
    public boolean canUserBeInRole(long userId, Role role) {
        Validate.isTrue(userId > 0);
        Validate.notNull(role);

        User user = userDao.findById(userId);
        Validate.notNull(user);

        return user.getRoles().contains(role);
    }

    /**
     * Check if current user can be in role
     *
     * @param role
     * @return
     */
    @Override
    public boolean canCurrentUserBeInRole(Role role) {
        long currentUserId = authentication.getCurrentUser().getId();
        return canUserBeInRole(currentUserId, role);
    }

    /**
     * List user roles
     *
     * @return
     */
    @Override
    public List<Role> listUserRoles() {
        long currentUserId = authentication.getCurrentUser().getId();

        User user = userDao.findById(currentUserId);
        Validate.notNull(user);

        return user.getRoles();
    }

}
